﻿using System;

namespace Silver3D.Math
{
    public struct Vec3
    {
        public static Vec3 Zero { get { return new Vec3(0.0, 0.0, 0.0); } }
        public static Vec3 One { get { return new Vec3(1.0, 1.0, 1.0); } }
        public static Vec3 XAxis { get { return new Vec3(1.0, 0.0, 0.0); } }
        public static Vec3 YAxis { get { return new Vec3(0.0, 1.0, 0.0); } }
        public static Vec3 ZAxis { get { return new Vec3(0.0, 0.0, 1.0); } }

        private double x, y, z;

        public double X { get { return x; } set { x = value; } }
        public double Y { get { return y; } set { y = value; } }
        public double Z { get { return z; } set { z = value; } }

        public double LengthSquared
        {
            get { return Dot(this); }
        }

        public double Length
        {
            get
            {
                double lsq = LengthSquared;
                if (lsq == 0.0) return 0.0;
                return System.Math.Sqrt(lsq);
            }
        }

        public Vec3 Unit
        {
            get { return new Vec3(this) / Length; }
        }

        public Vec3(double xyz)
            : this(xyz, xyz, xyz)
        {
        }

        public Vec3(double x, double y)
            : this(x, y, 1.0)
        {
        }

        public Vec3(double x, double y, double z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Vec3(Vec3 copy)
            : this(copy.x, copy.y, copy.z)
        {
        }

        public Vec3(Vec2 v2)
            : this(v2.X, v2.Y, 1.0)
        {
        }

        public Vec3(Vec4 v4)
            : this(v4.X, v4.Y, v4.Z)
        {
        }

        public double this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0: return x;
                    case 1: return y;
                    case 2: return z;
                    default: throw new ArgumentOutOfRangeException("index");
                }
            }
            set
            {
                switch (index)
                {
                    case 0: x = value; break;
                    case 1: y = value; break;
                    case 2: z = value; break;
                    default: throw new ArgumentOutOfRangeException("index");
                }
            }
        }

        public static Vec3 operator -(Vec3 v)
        {
            return new Vec3(-v.x, -v.y, -v.z);
        }

        public static Vec3 operator -(double lhs, Vec3 rhs)
        {
            return new Vec3(lhs) - rhs;
        }

        public static Vec3 operator -(Vec3 lhs, double rhs)
        {
            return lhs - new Vec3(rhs);
        }

        public static Vec3 operator -(Vec3 lhs, Vec3 rhs)
        {
            return new Vec3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z);
        }

        public static Vec3 operator +(Vec3 v)
        {
            return new Vec3(v);
        }

        public static Vec3 operator +(double lhs, Vec3 rhs)
        {
            return new Vec3(lhs) + rhs;
        }

        public static Vec3 operator +(Vec3 lhs, double rhs)
        {
            return lhs + new Vec3(rhs);
        }

        public static Vec3 operator +(Vec3 lhs, Vec3 rhs)
        {
            return new Vec3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z);
        }

        public static Vec3 operator *(double s, Vec3 rhs)
        {
            return new Vec3(s * rhs.x, s * rhs.y, s * rhs.z);
        }

        public static Vec3 operator *(Vec3 lhs, double s)
        {
            return new Vec3(s * lhs.x, s * lhs.y, s * lhs.z);
        }

        public static Vec3 operator *(Vec3 lhs, Vec3 rhs)
        {
            return new Vec3(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z);
        }

        public static Vec3 operator /(double s, Vec3 rhs)
        {
            double inv = 1.0 / s;
            return inv * rhs;
        }

        public static Vec3 operator /(Vec3 lhs, double s)
        {
            double inv = 1.0 / s;
            return lhs * inv;
        }

        public static Vec3 operator /(Vec3 lhs, Vec3 rhs)
        {
            return new Vec3(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z);
        }

        public static bool operator ==(Vec3 a, Vec3 b)
        {
            return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
        }

        public static bool operator !=(Vec3 a, Vec3 b)
        {
            return (a.x != b.x) || (a.y != b.y) || (a.z != b.z);
        }

        public static bool operator <(Vec3 a, Vec3 b)
        {
            return a.LengthSquared < b.LengthSquared;
        }

        public static bool operator >(Vec3 a, Vec3 b)
        {
            return a.LengthSquared > b.LengthSquared;
        }

        public static bool operator <=(Vec3 a, Vec3 b)
        {
            return a.LengthSquared <= b.LengthSquared;
        }

        public static bool operator >=(Vec3 a, Vec3 b)
        {
            return a.LengthSquared >= b.LengthSquared;
        }

        public double Dot(Vec3 rhs)
        {
            return x * rhs.x + y * rhs.y + z * rhs.z;
        }

        public Vec3 Cross(Vec3 rhs)
        {
            return new Vec3(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x);
        }

        public Vec2 ToVec2()
        {
            return new Vec2(this) / z;
        }

        public override string ToString()
        {
            return string.Format("Vec3({0},{1},{2})", x, y, z);
        }

        public bool IsEquivalent(Vec3 v)
        {
            if (v == null) return false;
            return Util.IsEquivalent(x, v.x) && Util.IsEquivalent(y, v.y) && Util.IsEquivalent(z, v.z);
        }

        public override bool Equals(object obj)
        {
            return (obj != null) && (obj is Vec3) && ((Vec3)obj) == this;
        }

        public override int GetHashCode()
        {
            return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode();
        }
    }
}
